﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Kokomo.Mirror.CliMetadata
{
	class CliBinder : Binder
	{
		static CliBinder()
		{
			Instance = new CliBinder();
		}

		public static readonly CliBinder Instance;

		private CliBinder()
		{
			conversionLists = new Dictionary<Type, Type[]>();
			conversionLists[typeof(char)] = new Type[] {
				typeof(ushort),
				typeof(uint),
				typeof(int),
				typeof(ulong),
				typeof(long),
				typeof(float),
				typeof(double)
			};
			conversionLists[typeof(byte)] = new Type[] {
				typeof(char),
				typeof(ushort),
				typeof(short),
				typeof(uint),
				typeof(int),
				typeof(ulong),
				typeof(long),
				typeof(float),
				typeof(double)
			};
			conversionLists[typeof(sbyte)] = new Type[] {
				typeof(short),
				typeof(int),
				typeof(long),
				typeof(float),
				typeof(double)
			};
			conversionLists[typeof(ushort)] = new Type[] {
				typeof(uint),
				typeof(int),
				typeof(ulong),
				typeof(long),
				typeof(float),
				typeof(double)
			};
			conversionLists[typeof(short)] = new Type[] {
				typeof(int),
				typeof(long),
				typeof(float),
				typeof(double)
			};
			conversionLists[typeof(uint)] = new Type[] {
				typeof(ulong),
				typeof(long),
				typeof(float),
				typeof(double)
			};
			conversionLists[typeof(int)] = new Type[] {
				typeof(long),
				typeof(float),
				typeof(double)
			};
			conversionLists[typeof(ulong)] = new Type[] {
				typeof(float),
				typeof(double)
			};
			conversionLists[typeof(long)] = new Type[] {
				typeof(float),
				typeof(double)
			};
			conversionLists[typeof(float)] = new Type[] { typeof(double) };
		}

		private Dictionary<Type, Type[]> conversionLists;

		public override FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] match, object value, System.Globalization.CultureInfo culture)
		{
			throw new NotImplementedException();
		}

		public override MethodBase BindToMethod(BindingFlags bindingAttr, MethodBase[] match, ref object[] args, ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, string[] names, out object state)
		{
			throw new NotImplementedException();
		}

		public override object ChangeType(object value, Type type, System.Globalization.CultureInfo culture)
		{
			throw new NotImplementedException();
		}

		public override void ReorderArgumentArray(ref object[] args, object state)
		{
			throw new NotImplementedException();
		}

		public bool IsAssignable(
			Type fromType,
			Type toType
			)
		{
			if ((object)fromType == null) throw new ArgumentNullException("fromType");
			if ((object)toType == null) throw new ArgumentNullException("toType");

			if (fromType == toType) return true;
			if (toType.IsAssignableFrom(fromType)) return true;

			Type[] targetTypes;
			if (this.conversionLists.TryGetValue(fromType, out targetTypes))
			{
				if (targetTypes.Contains(toType))
					return true;
			}

			return false;
		}

		public override MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[] modifiers)
		{
			if (types == null)
				return match.FirstOrDefault();

			MethodBase exactMatch = null;
			foreach (var method in match)
			{
				ParameterInfo[] parameters = method.GetParameters();

				if (parameters.Length == types.Length)
				{
					bool isMatch = true;
					for (int i = 0; i < parameters.Length; i++)
					{
						Type argumentType = types[i];
						Type parameterType = parameters[i].ParameterType;
						if (argumentType.AssemblyQualifiedName != parameterType.AssemblyQualifiedName)
						{
							isMatch = false;
							break;
						}
					}

					if (isMatch)
					{
						exactMatch = method;
						break;
					}
				}
			}

			if (exactMatch == null)
			{
				foreach (var method in match)
				{
					ParameterInfo[] parameters = method.GetParameters();

					if (parameters.Length == types.Length)
					{
						bool isMatch = true;
						for (int i = 0; i < parameters.Length; i++)
						{
							Type argumentType = types[i];
							Type parameterType = parameters[i].ParameterType;
							if (!IsAssignable(argumentType, parameterType))
							{
								isMatch = false;
								break;
							}
						}

						if (isMatch)
						{
							exactMatch = method;
							break;
						}
					}
				}
			}

			return exactMatch;
		}

		public override PropertyInfo SelectProperty(BindingFlags bindingAttr, PropertyInfo[] match, Type returnType, Type[] indexes, ParameterModifier[] modifiers)
		{
			return match[0];
		}
	}
}
